﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Neuron
{
	public class CustomSkelConfigParseObj {
	}

	// for strategy1 {{
	public class CustomSkelConfigRuleUnit : CustomSkelConfigParseObj {
		public float weight;
		public int bone;
	}

	public class CustomSkelConfigRule : CustomSkelConfigParseObj {
		public int boneRef;
		public List<CustomSkelConfigRuleUnit> ruleUnits = new List<CustomSkelConfigRuleUnit> ();
	}

	public class CustomSkelConfigStrategy1 : CustomSkelConfigParseObj {
		public Dictionary<int, CustomSkelConfigRule> rules = new Dictionary<int, CustomSkelConfigRule> ();
	}
	// for strategy1 }}

	// for strategy2 {{
	public class CustomSkelConfigPositionRange : CustomSkelConfigParseObj {
		public int bone;
		public Vector3 minPos;
		public Vector3 maxPos;
	}

	public class CustomSkelConfigBonePointController : CustomSkelConfigParseObj {
		public int boneRef;
		public float minYRot;
		public float maxYRot;

		public int rootBone;
		public float root_minYRot;
		public float root_maxYRot;

		public List<CustomSkelConfigPositionRange> subBones = new List<CustomSkelConfigPositionRange> ();
	}

	public delegate float RootControlPoint(float boneRef_yRot);
	public delegate Vector3 SubControlPoint(float rootCtrlPoint_yRot);

	public class CustomSkelConfigConstraint {
		public int arg_bone;
		public RootControlPoint rootCtrlPointAction;
		public List<SubControlPoint> subCtrlPointAction = new List<SubControlPoint> ();
	}

	public class CustomSkelConfigStrategy2 : CustomSkelConfigParseObj {
		public Dictionary<int, CustomSkelConfigBonePointController> controller = new Dictionary<int, CustomSkelConfigBonePointController> ();
		public Dictionary<int, CustomSkelConfigConstraint> constraints = new Dictionary<int, CustomSkelConfigConstraint> ();

        public void calculateConstraints() {
			var iter = controller.GetEnumerator ();
			while (iter.MoveNext ()) {
				CustomSkelConfigConstraint constraint = new CustomSkelConfigConstraint ();
				constraint.arg_bone = iter.Current.Key;
				// build closure
				float coff_term, const_term;
				linearCoff (
					iter.Current.Value.minYRot,
					iter.Current.Value.maxYRot,
					iter.Current.Value.root_minYRot,
					iter.Current.Value.root_maxYRot,
					out coff_term,
					out const_term);

				constraint.rootCtrlPointAction = arg_yRot => {
                    //float linear_ratio = (arg_yRot - iter.Current.Value.minYRot) / (iter.Current.Value.maxYRot - iter.Current.Value.minYRot);
                    //if (linear_ratio < 0) {
                    //	return iter.Current.Value.root_minYRot;
                    //} else if (linear_ratio > 1) {
                    //	return iter.Current.Value.root_maxYRot;
                    //} else {
                    //Debug.Log(string.Format("coff_term: {0}  const_term: {1}  arg_yRot: {2}", coff_term, const_term, arg_yRot));
                    return coff_term * arg_yRot + const_term;
                    //float ret_val = coff_term * arg_yRot + const_term;
                    //Debug.Log(string.Format("joint4_yRot: {0}", ret_val));
                    //return ret_val;
                    //}
                };

				// sub 
				var sub_iter = iter.Current.Value.subBones.GetEnumerator();
				while (sub_iter.MoveNext ()) {
					float x_coff_term, x_const_term;
					linearCoff (
						iter.Current.Value.root_minYRot,
						iter.Current.Value.root_maxYRot,
						sub_iter.Current.minPos.x,
						sub_iter.Current.maxPos.x,
						out x_coff_term, 
						out x_const_term);
					
					float y_coff_term, y_const_term;
					linearCoff (
						iter.Current.Value.root_minYRot,
						iter.Current.Value.root_maxYRot,
						sub_iter.Current.minPos.y,
						sub_iter.Current.maxPos.y,
						out y_coff_term, 
						out y_const_term);

					float z_coff_term, z_const_term;
					linearCoff (
						iter.Current.Value.root_minYRot,
						iter.Current.Value.root_maxYRot,
						sub_iter.Current.minPos.z,
						sub_iter.Current.maxPos.z,
						out z_coff_term, 
						out z_const_term);

					Vector3 pos_coff_term = new Vector3 (x_coff_term, y_coff_term, z_coff_term);
					Vector3 pos_const_term = new Vector3 (x_const_term, y_const_term, z_const_term);

					SubControlPoint subAction = arg_yRot => {
						//float linear_ratio = (arg_yRot - iter.Current.Value.root_minYRot) / (iter.Current.Value.root_maxYRot - iter.Current.Value.root_minYRot);
                        /*if (linear_ratio < 0) {
							return sub_iter.Current.minPos;
						} else if (linear_ratio > 1) {
							return sub_iter.Current.maxPos;
						} else {*/
							return pos_coff_term * arg_yRot + pos_const_term;
						//}
					};
					constraint.subCtrlPointAction.Add (subAction);
				}

				constraints.Add (constraint.arg_bone, constraint);
			}
		}

		public static void linearCoff(float a1, float a2, float v1, float v2, out float coff_term, out float const_term) {
			float delta_arg = a2 - a1;
			float delta_val = v2 - v1;
			coff_term = delta_val / delta_arg;
			const_term = v1 - a1 * delta_val / delta_arg;
		}
	}
	// for strategy2 }}

	public class CustomSkelConfig {
		public CustomSkelConfigStrategy1 strategy_1 = new CustomSkelConfigStrategy1 ();
		public CustomSkelConfigStrategy2 strategy_2 = new CustomSkelConfigStrategy2 ();


        ////////////////////////////////////////////////
        // Internal use

		int L;
		int P;	// Position
		string C;	// content
		Stack<CustomSkelConfigParseObj> S = new Stack<CustomSkelConfigParseObj>();

		// debug use {{
		int ROW;
		int COL;

		int LexicalElemRow;
		int LexicalElemCol;
		// debug use }}

		void prolog(string content) {
			C = content;
			L = C.Length;
			P = -1;
			ROW = 1;
			COL = 0;
			getChar ();
			skipWhite ();

            S.Clear();
			strategy_1.rules.Clear ();
			strategy_2.controller.Clear ();
			strategy_2.constraints.Clear ();
		}

        void epilog()
        {
            strategy_2.calculateConstraints();
        }
			
		public bool parse(string content) {
			prolog (content);

			if (isEOF ()) {
				debuginfo ("The content is empty!");
				return true;
			}
				
			string s_name;
			while (!isEOF ()) {
				if (match ('[') &&
				    name (out s_name) &&
				    match (']')) {
					if (s_name == "s1") {
						if (!strategy1 ()) {
							debuginfo ("parse strategy_1 failed!");
							return false;
						}
					} else if (s_name == "s2") {
						if (!strategy2 ()) {
							debuginfo ("parse strategy_2 failed!");
							return false;
						}
					} else {
						debuginfo ("unknown strategy name");
						expected ("STRATEGY::NAME s1 or s2");
						return false;
					}
				} else {
					return false;
				}
			}

            epilog();

			return true;
		}

		bool strategy1() {
			// logic {{
			S.Push (strategy_1);
			// logic }}

			if (!rules ()) {
				return false;
			}

			S.Pop ();
			return true;
		}

		bool rules() {
			CustomSkelConfigStrategy1 _strategy = S.Peek () as CustomSkelConfigStrategy1;
			while (!isEOF () && C [P] != '[') {
				if (!rule ()) {
					return false;
				}
				CustomSkelConfigRule _rule = S.Pop () as CustomSkelConfigRule;
				_strategy.rules.Add (_rule.boneRef, _rule);
			}

			return true;	
		}

		bool rule() {
			CustomSkelConfigRule _rule = new CustomSkelConfigRule();
			S.Push (_rule);

            return (
                ruleHead() &&
                match('=') &&
                ruleBody()
                );
        }

        bool ruleHead() {
			int boneIndex;
			if (!bone (out boneIndex)) {
				return false;
			}

			CustomSkelConfigRule _rule = S.Peek () as CustomSkelConfigRule;
			_rule.boneRef = boneIndex;
			return true;
		}

		bool ruleBody() {
			CustomSkelConfigRule _rule = S.Peek () as CustomSkelConfigRule;

			if (!ruleUnit ()) {
				debuginfo ("At least need one rule unit");
				return false;
			}

			_rule.ruleUnits.Add (S.Pop () as CustomSkelConfigRuleUnit);

			bool ruleBody_complete = false;
			while (!isEOF () && C[P] != ';') {
				if (!match('+') || !ruleUnit ()) {
					return false;
				}
				_rule.ruleUnits.Add (S.Pop () as CustomSkelConfigRuleUnit);
			}

			if (!isEOF ()) {
				match (';');
				ruleBody_complete = true;
			}

			if (!ruleBody_complete) {
				debuginfo ("ruleBody is incomplete!");
				return false;
			} else {
				return true;
			}
		}

		bool ruleUnit() {
			if (isEOF ()) {
				return false;
			}

			CustomSkelConfigRuleUnit _ruleUnit = new CustomSkelConfigRuleUnit ();
			S.Push (_ruleUnit);

			if (char.IsLetter (C [P])) {
                return (
                    bone(out _ruleUnit.bone) &&
                    match('*') &&
                    number(out _ruleUnit.weight)
                    );
            }
            else if (char.IsDigit (C [P])) {
                return (
                    number(out _ruleUnit.weight) &&
                    match('*') &&
                    bone(out _ruleUnit.bone)
                    );

			} else {
				return false;
			}
		}

		bool strategy2() {
			// logic {{
			S.Push (strategy_2);
			// logic }}

			if (!rules_2 ()) {
				return false;
			}

			S.Pop ();
			return true;
		}

		bool rules_2() {
			CustomSkelConfigStrategy2 _strategy = S.Peek () as CustomSkelConfigStrategy2;

			while (!isEOF () && C[P] != '[') {
				if (!rule_2 ()) {
					return false;
				}

				CustomSkelConfigBonePointController controller = S.Pop () as CustomSkelConfigBonePointController;
				_strategy.controller.Add (controller.boneRef, controller);
			}

			return true;
		}

		bool rule_2() {
			CustomSkelConfigBonePointController ctrl = new CustomSkelConfigBonePointController ();
			S.Push (ctrl);

			// The 1st pair: (A,B) {{
			if (!bone (out ctrl.boneRef) ||
			    !match ('{') ||
			    !float_pair (out ctrl.minYRot, out ctrl.maxYRot)) {
				return false;
			}
            // Coordinate system convert: maya to unity3d
            ctrl.minYRot = -ctrl.minYRot;
            ctrl.maxYRot = -ctrl.maxYRot;
			// }}

			// The 2nd pair: bone(C,D) {{
			if (!bone (out ctrl.rootBone) ||
			    !float_pair (out ctrl.root_minYRot, out ctrl.root_maxYRot)) {
				return false;
			}
            // Coordinate system convert: maya to unity3d
            ctrl.root_minYRot = -ctrl.root_minYRot;
            ctrl.root_maxYRot = -ctrl.root_maxYRot;
			// }}

			// The 3rd pair((x,y,z),(x,y,z)) [+ ...] {{
			if (!rule_unit2 ()) {
				return false;
			}
			ctrl.subBones.Add (S.Pop () as CustomSkelConfigPositionRange);
			while (!isEOF () && C [P] == '+') {
				if (!match('+') || !rule_unit2 ()) {
					return false;
				}
				ctrl.subBones.Add (S.Pop () as CustomSkelConfigPositionRange);
			}
            // }}

            return match('}');
		}

		bool float_pair(out float n1, out float n2) {
			n1 = n2 = 0.0f;
            return (
                match('(') &&
                number(out n1) &&
                match(',') &&
                number(out n2) &&
                match(')')
                );
        }

        bool float_triple(out float n1, out float n2, out float n3) {
			n1 = n2 = n3 = 0.0f;
            return (
                match('(') &&
                number(out n1) &&
                match(',') &&
                number(out n2) &&
                match(',') &&
                number(out n3) &&
                match(')')
                );
		}

		bool rule_unit2 () {
			CustomSkelConfigPositionRange _unit = new CustomSkelConfigPositionRange ();
			S.Push (_unit);

			// the third pair((x,y,z),(x,y,z)) {{
			if (!bone (out _unit.bone) ||
			    !match ('(') ||
				!float_triple (out _unit.minPos.x, out _unit.minPos.y, out _unit.minPos.z) ||
			    !match (',') ||
				!float_triple (out _unit.maxPos.x, out _unit.maxPos.y, out _unit.maxPos.z) ||
			    !match (')')) {
				return false;
			}
			// size unit convert. Default: Maya=cm, Unity3D=m
			_unit.minPos /= 100;
			_unit.maxPos /= 100;
            // coordinate system convert. 
            // Default: Maya=RightHand(X->Left,Y->Up,Z->Forward)
            // Default: Unity=LeftHand(X->Right,Y->Up,Z->Forward)
            _unit.minPos.x = -_unit.minPos.x;
            _unit.maxPos.x = -_unit.maxPos.x;
			// }}
			return true;
		}

		bool bone(out int index) {
			index = -1;

			string boneName;
			if (!name(out boneName)) {
				return false;
			}

			index = NeuronHelper.GetBoneIndex (boneName);
			if (index == -1) {
                debuginfo(string.Format("Extra bone detected: {0}", boneName));
                index = CustomSkelHelper.AddExtraBone(boneName);
                return true;
			}

			return true;
		}

		bool name(out string s_name) {
			LexicalElemRow = ROW;
			LexicalElemCol = COL;

			s_name = "";
			while (!isEOF() && (char.IsLetterOrDigit (C [P]) || C [P] == '_')) {
				s_name += C [P];
				getChar ();
			}

			skipWhite ();
			if (string.IsNullOrEmpty (s_name)) {
				expected ("NAME");
				return false;
			}
			return true;
		}

		bool number(out float num) {
			LexicalElemRow = ROW;
			LexicalElemCol = COL;

			num = 0.0f;
			if (isEOF ()) {
				expected ("NUMBER");
				return false;
			}

			string s_num = "";
			if (C [P] == '-') {
				s_num += C [P];
				getChar ();
				skipWhite ();
			}
				
			while (!isEOF () && (char.IsDigit (C [P]) || C [P] == '.')) {
				s_num += C [P];
				getChar ();
			}

			if (string.IsNullOrEmpty (s_num) ||
				!float.TryParse (s_num, out num)) {
				expected ("NUMBER");
				return false;
			}

			skipWhite ();
			return true;
		}

		bool match(char ch) {
			if (!isEOF() && ch == C [P]) {
				getChar ();
				skipWhite ();
				return true;
			} else {
				LexicalElemRow = ROW;
				LexicalElemCol = COL;

				expected (ch);
				return false;
			}
		}

		bool matchString(string str) {
			if (L-P >= str.Length) {
				for (int i = 0; i < str.Length; ++i) {
					if (C [P] == str [i]) {
						getChar ();
					} else {
						LexicalElemRow = ROW;
						LexicalElemCol = COL;

						expected (str [i]);
						return false;
					}
				}
				skipWhite ();
				return true;
			}
			else {
				LexicalElemRow = ROW;
				LexicalElemCol = COL;

				expected (str);
				return false;
			}
		}

		void skipWhite() {
			while (!isEOF() && isWhiteSpace (C [P])) {
				getChar ();
			}
		}

		bool isWhiteSpace(char ch) {
            // #ifdef WIN32
			if (ch == '\n') {
				++ROW;
				COL = 0;
				return true;
			}
		
			return char.IsWhiteSpace (ch);
		}

		void getChar() {
			++P;
			++COL;
		}

		bool isEOF() {
			return P >= L;
		}

		void expected(char key) {
			Debug.Log (string.Format ("The '{0}' expected at position [{1}, {2}], parse failed...", key, LexicalElemRow, LexicalElemCol));
		}

		void expected(string key) {
			Debug.Log (string.Format ("The '{0}' expected at position [{1}, {2}], parse failed...", key, LexicalElemRow, LexicalElemCol));
		}

		void debuginfo(string info) {
			Debug.Log (string.Format("Pos:[{0}, {1}] {2}", LexicalElemRow, LexicalElemCol, info));
		}

	}


}
